﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Newtonsoft.Json;
using System.Xml.Serialization;

namespace ilanver.Core.DomainModel
{
    [Serializable]
    public class DomainSignatureAttribute : Attribute { }

    public abstract class EntityWithTypedId<T> : ValidatableObject, IEntityWithTypedId<T>
    {
        private int? cachedHashcode;
        private const int HASH_MULTIPLIER = 31;

        [XmlIgnore]
        [JsonProperty]
        public virtual T Id { get; protected set; }

        public virtual bool IsTransient()
        {
            return Id == null || Id.Equals(default(T));
        }

        protected override IEnumerable<PropertyInfo> GetTypeSpecificSignatureProperties()
        {
            return GetType().GetProperties()
                .Where(p => Attribute.IsDefined(p, typeof(DomainSignatureAttribute), true));
        }

        public override bool Equals(object obj)
        {
            EntityWithTypedId<T> compareTo = obj as EntityWithTypedId<T>;

            if (ReferenceEquals(this, compareTo))
                return true;

            if (compareTo == null || !GetType().Equals(compareTo.GetTypeUnproxied()))
                return false;

            if (HasSameNonDefaultIdAs(compareTo))
                return true;


            return IsTransient() && compareTo.IsTransient() && HasSameObjectSignatureAs(compareTo);
        }

        public override int GetHashCode()
        {
            if (cachedHashcode.HasValue)
                return cachedHashcode.Value;
            if (IsTransient())
                cachedHashcode = base.GetHashCode();
            else
            {
                unchecked
                {
                    int hasCode = GetType().GetHashCode();
                    cachedHashcode = (hasCode * HASH_MULTIPLIER) ^ Id.GetHashCode();
                }
            }

            return cachedHashcode.Value;
        }

        public bool HasSameNonDefaultIdAs(EntityWithTypedId<T> compareTo)
        {
            return !IsTransient() && !compareTo.IsTransient() && (Id.Equals(compareTo.Id));
        }
    }
}
